import sys, os
import traceback
import time
import wx

import cPickle
import osg, osgGA
import Constants




class SubController:
    """
        A SubController is always linked to a OSGFrame and vice versa.
        The subcontroller takes care about all that happens within that OSGFrame and
        controlls it.
    """
    
    def __init__(self, maincontroller, framenumber):
        """
            Parameters:
                maincontroller: The one MainController of the program.
                framenumber: The number of the OSGFrame (according to the list of numbers
                    on the venue's Application Service) and hence also the number of
                    this subcontroller.
        """
        
        ##############################
        # start: list of all members #
        ##############################
        
        self.maincontroller = maincontroller
        self.sharedAppClient = maincontroller.sharedAppClient
        self.framenumber = framenumber
        self.framenumberStr = str(framenumber)
        self.mode = Constants.MODE_STANDARD
        
        # The OSGFrame that this SubController takes care of.
        self.frame = None
        
        # The osg::Viewer of the OSGFrame
        self.viewer = None
        
        # In this variable the view matrix of the scene is stored when switching to
        # the telepointer mode. This is done so that it can be restored after leaving
        # the telepointer mode.
        self.viewMatrix = None
        
        # The root node of the scene graph is a switch node which has
        # two children with the following indices:
        #   0: the node that contains the model
        #   1: the node that contains all the 2D telepointers
        self.rootNode = None
        
        # Child node of the root node. It contains all the 2D telepointers
        self.telepointerNode = None
        
        # This timer controls the continuous movement of the model.
        # It will be deleted as soon as it is possible to override the
        # handle function of the TrackballManipulator.
        # The self.matrixFromLastTimer belongs to this timer.
        self.timerActive = False
        self.matrixFromLastTimer = osg.Matrixd()
        
#        # StateSetManipulator in the OSGFrame. Not yet implemented.
#        self.stateSetManipulator = None

        # The node of the model. It is a children of the self.rootNode.
        # It is used by the OSGHandler.
        self.modelNode = None
        
        # Used by OSGHandler to switch to and from the wireframe mode.
        self.wireframe = False
        
        # Dictionary of all the MatrixTransform nodes of the participants.
        # Every participant has a MatrixTransform node that has exactly one
        # child: the telepointer nodes of that participant.
        # The MatrixTransform nodes are children of the self.telepointerNode.
        self.transform2dNodes = {}
        
        ############################
        # end: list of all members #
        ############################
        
    def RegisterFrameAndStartRunning(self, frame):
        self.frame = frame
        self.viewer = frame.canvas.viewer
        
        print 'registering eventcallback', 'changeView' + self.framenumberStr
        self.sharedAppClient.RegisterEventCallback('changeView' + self.framenumberStr, 
                                                   self.HandleRemoteChangeViewEvent )
        self.sharedAppClient.RegisterEventCallback('changeMode' + self.framenumberStr, 
                                                   self.HandleRemoteChangeModeEvent)
        self.sharedAppClient.RegisterEventCallback('change2dPointerPos' + self.framenumberStr, 
                                                   self.HandleRemoteChange2dPointerPosEvent)
        self.sharedAppClient.RegisterEventCallback('FrameClose' + self.framenumberStr, 
                                                   self.HandleFrameCloseEvent)
        
        for (participantId, participantData) in self.maincontroller.participantsData.items():        
            # participantData[3] is the telepointer geomaetry node
            self.AddNodeTo2dTelepointerNode( participantId, participantData['telepointer2dNode'] )
        
        # for the timers
        self.running = True
        
        # find out if there is already a view set for the file
        pickledData = self.sharedAppClient.GetData( 'currentView' + self.framenumberStr )
        if pickledData != '':
            data = cPickle.loads( str(pickledData) )
            vecTrans = osg.Vec3d( data[0], data[1], data[2] )
            quatRot = osg.Quat( data[3], data[4], data[5], data[6] )
            viewMatrix = osg.Matrixd( quatRot )
            viewMatrix.setTrans( vecTrans )
            self.viewer.getCameraManipulator().setByMatrix(viewMatrix)
            
            
    def AddNodeTo2dTelepointerNode(self, participantId, node):
        """
            Adds a node to the group node of the 2D telepointers.
            Parameters:
                participantId: Public ID of the participant the node stands for.
                node: The node to add.
        """
        transformNode = osg.MatrixTransform()
        transformNode.addChild( node )
        self.transform2dNodes[participantId] = transformNode
        self.telepointerNode.addChild(transformNode)
        
    
    def OnFrameClose(self, event):
        """
        This method is called when a OSG viewer window gets closed by the user.
        It notifies the main controller to remove this frame from the session.
        """
        self.maincontroller.CloseFrame(self.framenumber)


    def HandleFrameCloseEvent(self, event):
        self.frame.Destroy()
        self.running = False
        self.maincontroller.ClosedFrame(self.framenumber)
        
        
        
#    def UpdateViewOnVenue(self):
#        try:
#            if not self.running:
#                return
#            
#            if self.mode == Controller.MODE_STANDARD:
#                view = self.viewer.getCameraManipulator().getMatrix()
#                vecTrans = view.getTrans()
#                quatRot = view.getRotate()
#                data = ( self.sharedAppClient.GetPublicId(),
#                         vecTrans.x(), vecTrans.y(), vecTrans.z(), quatRot.x(), quatRot.y(),
#                         quatRot.z(), quatRot.w() )
#                pickledData = cPickle.dumps(data)
#                self.sharedAppClient.SetData('currentView' + self.framenumberStr, pickledData)
#    
#            timer = threading.Timer( 5, self.UpdateViewOnVenue )
#            timer.start()
#        except:
#            pass
        
        
    def OnTimer(self):
        while self.running and self.timerActive and self.mode == Constants.MODE_STANDARD:
            try:
                matrix = self.viewer.getCameraManipulator().getMatrix()
                
                # the viewing matrix didn't change since the last timer event,
                # so no new timer will be activated
                if matrix == self.matrixFromLastTimer:
                    return
                
                self.matrixFromLastTimer = matrix
                self.HandleLocalViewChange( matrix )
                time.sleep(0.1)
            except:
                print "Unexpected error in rotation update timer (" + \
                        str( sys.exc_info()[0] ) + '):', sys.exc_info()[1]
                traceback.print_tb(sys.exc_info()[2], None, sys.stdout)
            
    
    def HandleLocalChange2dPointerPos(self, x, y):
        transformNode = self.transform2dNodes[self.maincontroller.publicId]
        
        width, height = self.frame.canvas.GetClientSize()
        transformNode.setMatrix( osg.Matrixd.translate(osg.Vec3(x * width, y * height, 0)) )
        
        data = ( self.maincontroller.publicId, x, y )
        dataString = cPickle.dumps(data)
        self.sharedAppClient.SendEvent( 'change2dPointerPos' + self.framenumberStr, dataString)        
    
    
    def HandleRemoteChange2dPointerPosEvent(self, event):
        # don't handle my own view changes
        if event.senderId == self.sharedAppClient.GetPublicId():
            return
        
        publicId, x, y = cPickle.loads( str(event.data) )
        width, height = self.frame.canvas.GetClientSize()
        self.transform2dNodes[publicId].setMatrix( osg.Matrixd.translate( osg.Vec3(x * width, y * height, 0) ) )
        
        
    def changeMode(self):
        
        # deactivate timer
        self.maincontroller.timerActive = False
        
        if self.mode == Constants.MODE_STANDARD:
            self.frame.SetCursor(wx.StockCursor(wx.CURSOR_BLANK))  # Hide the mouse cursor
            
            self.mode = Constants.MODE_TELEPOINTER
            self.rootNode.setValue(1, True)
            
            # the telepointers shouldn't be visible at the beginning
            for transformNode in self.transform2dNodes.values():
                transformNode.setMatrix(
                    osg.Matrixd.translate(osg.Vec3(-100, -100, 0)) )
            
            # save current camera position
            self.viewMatrix = self.viewer.getCameraManipulator().getMatrix()
            self.viewer.setCameraManipulator(None)
            
            self.sharedAppClient.SendEvent('changeMode' + self.framenumberStr, Constants.MODE_TELEPOINTER)
            
        elif self.mode == Constants.MODE_TELEPOINTER:
            self.frame.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))  # Show the mouse cursor
            
            self.mode = Constants.MODE_STANDARD
            tbm = osgGA.TrackballManipulator()
            self.viewer.setCameraManipulator(tbm)
            tbm.setByMatrix(self.viewMatrix)
            self.rootNode.setValue(1, False)
            self.sharedAppClient.SendEvent('changeMode' + self.framenumberStr, Constants.MODE_STANDARD)
            

    def HandleRemoteChangeModeEvent(self, event):
        # don't handle my own view changes
        if event.senderId == self.sharedAppClient.GetPublicId():
            return
        
        if event.data != self.mode :
            self.changeMode()
        
        
    def HandleLocalViewChange(self, view):
        vecTrans = view.getTrans()
        quatRot = view.getRotate()
        data = ( vecTrans.x(), vecTrans.y(), vecTrans.z(), quatRot.x(), quatRot.y(),
                 quatRot.z(), quatRot.w() )
        pickledData = cPickle.dumps(data)
        self.sharedAppClient.SendEvent('changeView' + self.framenumberStr, pickledData)
#        self.sharedAppClient.SetData('currentView', pickledData)
        
        
    def HandleRemoteChangeViewEvent(self, event):
        """
            Called whenever the view gets updated by another client than this one.
        """
        
        # don't handle my own view changes
        if event.senderId == self.sharedAppClient.GetPublicId():
            return
        
        data = cPickle.loads(str(event.data))
        
        vecTrans = osg.Vec3d( data[0], data[1], data[2] )
        quatRot = osg.Quat( data[3], data[4], data[5], data[6] )
        viewMatrix = osg.Matrixd( quatRot )
        viewMatrix.setTrans( vecTrans )
        
        cameraManipulator = self.viewer.getCameraManipulator()
        if cameraManipulator:
            cameraManipulator.setByMatrix(viewMatrix)
